/**************************************************************************************
*
*	Copyright (C) 2009 - 2012 Brent Andrew O. Chua - See LICENSE.txt for more info.
*		
*	Filename	-	RenderPathLightPrePass.cpp
*
*	Comments	-	See RenderPathLightPrePass.h
*
**************************************************************************************/

#include "../Include/RenderPathLightPrePass.h"
#include "../Include/Engine.h"
#include "../Include/WindowView.h"
#include "../Include/CameraRenderPathSettingsFP.h"
#include "../Include/RenderView.h"
#include "../Include/Renderer.h"
#include "../Include/Texture2D.h"
#include "../Include/GraphicsResourceManager.h"
#include "../Include/GlobalGraphicsStrings.h"
#include "../Include/ShaderResourceViewDesc.h"
#include "../Include/DepthStencilViewDesc.h"
#include "../Include/DepthStencilState.h"
#include "../Include/GlobalDirectory.h"
#include "../Include/ParameterBinderShaderResource.h"
#include "../Include/ParameterBinderMatrixRef.h"
#include "../Include/Model.h"
#include "../Include/GeometryGenerator.h"
#include "../Include/ComponentLight.h"
#include "../Include/Actor.h"

// Include render entries
#include "../Include/RenderEntryClearPipeline.h"
#include "../Include/RenderEntryList.h"
#include "../Include/RenderEntryModel.h"
#include "../Include/RenderEntryRenderTarget.h"
#include "../Include/RenderEntryViewport.h"
#include "../Include/RenderEntryClearRenderBuffers.h"
#include "../Include/RenderEntryPresent.h"
#include "../Include/RenderEntryDepthStencilState.h"
#include "../Include/RenderEntryBindParameters.h"
#include "../Include/RenderEntryLight.h"

namespace Pulse
{
	RenderPathLightPrePass::RenderPathLightPrePass( void )
	{
		m_pPointLightMat = PSX_NULL;
		m_pPointLightGeo = PSX_NULL;
	}

	RenderPathLightPrePass::~RenderPathLightPrePass( void )
	{

	}

	EErrorCode::Type RenderPathLightPrePass::Initialize( void )
	{
		// NOTE: This looks like a stupid security check. Fix this...
		if ( m_pPointLightMat )
			return EErrorCode::OKAY;

		// TODO: Load the techniques here...

		// TODO: Fix this in its proper place here...
		//// HACK: Set swapchain back and depth buffer
		//{
		//	RenderEntrySwapChain entry;
		//	SwapChain *pSwapChain = m_pRenderer->GetSwapchain();
		//	entry.SetSwapChain( pSwapChain );
		//	m_pRenderer->ExecuteRenderEntry( &entry );
		//	pSwapChain->Release();
		//}

		Engine *pEngine = Engine::GetInstance();
		Renderer *pRenderer = pEngine->GetRenderer();
		ParameterSystem *pParamSys = pRenderer->GetParameterSystem();
		GraphicsResourceManager *pGM = GraphicsResourceManager::GetInstance();

		//// HACK: Set viewport.
		//{
		//	const DisplayMode *pDisplaySettings = pEngine->GetDisplaySettings();
		//	RenderEntryViewport viewportEntry;
		//	Viewport viewport( 0, 0, (FLOAT)pDisplaySettings->width, (FLOAT)pDisplaySettings->height );
		//	viewportEntry.SetViewport( &viewport );
		//	pRenderer->ExecuteRenderEntry( &viewportEntry );
		//}	

		// Create light model instances.
		{
			// Create sphere material, geometry, then model.
			MaterialDesc matDesc;

			matDesc.SetToLoad( PSX_String("PointLight.mtl") );
			m_pPointLightMat = pGM->CreateMaterial( GFX_LP_SPHERE_LIGHT_MATERIAL, &matDesc );

			if ( m_pPointLightMat == PSX_NULL )
			{
				PSX_PushError( "Failed to create sphere light material instance." );
				return EErrorCode::GRAPHICS;
			}

			matDesc.SetToLoad( PSX_String("LightPrePass\\PointLightFront.mtl") );
			m_pPointLightFront = pGM->CreateMaterial( GFX_LP_SPHERE_LIGHT_MATERIAL_FRONT, &matDesc );

			if ( m_pPointLightFront == PSX_NULL )
			{
				PSX_PushError( "Failed to create sphere light front material instance." );
				return EErrorCode::GRAPHICS;
			}

			matDesc.SetToLoad( PSX_String("LightPrePass\\PointLightBack.mtl") );
			m_pPointLightBack = pGM->CreateMaterial( GFX_LP_SPHERE_LIGHT_MATERIAL, &matDesc );

			if ( m_pPointLightBack == PSX_NULL )
			{
				PSX_PushError( "Failed to create sphere light back material instance." );
				return EErrorCode::GRAPHICS;
			}
			matDesc.SetToLoad( PSX_String("LightPrePass\\PointLightQuad.mtl") );
			m_pPointLightQuad = pGM->CreateMaterial( GFX_LP_SPHERE_LIGHT_MATERIAL_QUAD, &matDesc );

			if ( m_pPointLightQuad == PSX_NULL )
			{
				PSX_PushError( "Failed to create sphere light quad material instance." );
				return EErrorCode::GRAPHICS;
			}

			m_pPointLightGeo = GeometryGenerator::CreateSphere( 1.0f, 5, 5, GFX_LP_SPHERE_LIGHT_GEOMETRY );

			if ( m_pPointLightGeo == PSX_NULL )
			{
				PSX_PushError( "Failed to create point (sphere) light geometry." );
			}

			m_pGeoQuad = GeometryGenerator::CreateFullscreenQuad();

			if ( m_pGeoQuad == PSX_NULL )
			{
				PSX_PushError( "Failed to create quad light geometry." );
			}
		}

		// Initialization succeeded
		return EErrorCode::OKAY;
	}

	void RenderPathLightPrePass::Cleanup( void )
	{
		// Release material and geo
		PSX_SafeRelease( m_pPointLightGeo );
		PSX_SafeRelease( m_pPointLightMat );
		PSX_SafeRelease( m_pPointLightFront );
		PSX_SafeRelease( m_pPointLightBack );
		PSX_SafeRelease( m_pPointLightQuad );
	}

	CameraRPSettings * RenderPathLightPrePass::FindCameraSettings( const ComponentCamera *pCamera )
	{
		if ( pCamera == PSX_NULL )
		{
			PSX_PushWarning( "pName is null." );
			return PSX_NULL;
		}

		for ( SIZE_T i = 0; i < m_viewRPSettings.GetSize(); ++i )
		{
			CameraRPSettings *pCamSettings = &m_viewRPSettings[i];
		
			if ( pCamera == pCamSettings->first )
			{
				return pCamSettings;
			}
		}

		return PSX_NULL;
	}

	EErrorCode::Type RenderPathLightPrePass::BuildRenderEntries( const Model *pModel, RenderEntryList *pRenderEntries )
	{
		// render entry list must be valid and empty
		if ( pModel == PSX_NULL || pRenderEntries == PSX_NULL )
		{
			return EErrorCode::INVALID_PARAMETER;
		}

		// Make sure the render entry we'll generate is valid.
		// TODO: This should return ...
		if ( pModel->VerifyRenderRequirements() == FALSE )
		{
			return EErrorCode::INVALID_PARAMETER;
		}

		// Reset render entries
		pRenderEntries->ClearRenderEntries();

		// Generate render entries
		Effect *pEffect = pModel->m_pMaterial->GetEffect();

		if ( pEffect == PSX_NULL )
		{
			PSX_PushWarning( "No effect attached to this model." );
			return EErrorCode::_ERROR;
		}

		RenderTechnique *pTech = pEffect->GetTechnique();

		if ( pTech == PSX_NULL )
		{
			PSX_PushWarning( "Effect has no render technique registered." );
			return EErrorCode::_ERROR;
		}

		TechGeoRenderInfo *pInfo = pModel->m_pGeometry->GetTechniqueRenderInfo( pTech );
		SIZE_T numPasses = pTech->GetNumRenderPasses();
		RenderEntryModel *pEntry = PSX_NULL;

		if ( pInfo == PSX_NULL )
		{
			PSX_PushWarning( "No render info associated with the render technique." );
			pTech->Release();
			return EErrorCode::_ERROR;
		}

		for ( SIZE_T i = 0; i < numPasses; ++i )
		{
			RenderPass *pPass = pTech->GetRenderPass( i );
			ERenderViewStage::Type stage = pPass->GetRenderViewStage();

			if ( stage == ERenderViewStage::MAIN || stage == ERenderViewStage::GBUFFER ||
				stage == ERenderViewStage::COMPOSITE || stage == ERenderViewStage::WINDOW_VIEW )
			{

				// Create render entry for eachs subgeometry
				SIZE_T numSubGeos = pModel->m_pGeometry->GetNumSubGeometries();
				for ( SIZE_T j = 0; j < numSubGeos; ++j )
				{
					pEntry = new RenderEntryModel;

					pEntry->SetStage( stage );

					// Add parameters

					// Add submaterial parameters
					ParameterContainer *pParams = pModel->m_pMaterial->GetSubMaterialParameters(j);

					if ( pParams && pParams->GetNumParameterBinders() )
						pEntry->m_params.PushBack( pParams );

					// Add material parameters
					pParams = pModel->m_pMaterial->GetMaterialParameters();

					if ( pParams && pParams->GetNumParameterBinders() )
						pEntry->m_params.PushBack( pParams );

					// Add model parameters
					if ( pModel->m_params.GetNumParameterBinders() )
						pEntry->m_params.PushBack( (ParameterContainer*)&pModel->m_params );

					IndexBuffer *pIB = pModel->m_pGeometry->GetIndexBuffer();

					pEntry->m_topology			= pModel->m_pGeometry->GetTopology();
					pEntry->m_pGeoRenderInfo	= pInfo->m_renderPassInfos[i];
					pEntry->m_pIndexBuffer		= pIB;
					pEntry->m_pGeoSubset		= pModel->m_pGeometry->GetGeometrySubset( j );
					pEntry->m_pRenderPass		= pTech->GetRenderPass( i );

					// Don't addref
					if ( pIB )
					{
						pIB->Release();
					}

					// Do error check make sure we have a valid geometry
					if ( pEntry->m_pGeoSubset == PSX_NULL )
					{
						PSX_PushError( "No geometry available for this render entry." );
					}

					pRenderEntries->AddRenderEntry( pEntry );
				}
			}
			else
			{
				PSX_PushWarning( "Render view stage specified by the shader pass not yet implemented." );
			}
		}

		pTech->Release();
		pEffect->Release();

		return EErrorCode::OKAY;
	}

	EErrorCode::Type RenderPathLightPrePass::BuildRenderEntries( ComponentLight *pLight )
	{
		PSX_PushError( "Work on this..." );
		//// Check parameters are valid
		//if ( pLight == PSX_NULL )
		//{
		//	return EErrorCode::INVALID_PARAMETER;
		//}

		//pRenderEntries->ClearRenderEntries();

		//if ( pLight->GetLightType() == ELight::POINT )
		//{
		//	GraphicsResourceManager *pGM = GraphicsResourceManager::GetInstance();
		//	RenderEntryLight *pREL = new RenderEntryLight;
		//	ModelDesc modelDesc;
		//	Model	*pModel;

		//	modelDesc.SetManual( m_pPointLightMat, m_pPointLightGeo );
		//	pModel = pGM->CreateModel( &modelDesc );

		//	if ( pModel == PSX_NULL )
		//	{
		//		PSX_PushError( "Failed to create point (sphere) light model." );
		//	}

		//	pREL->SetStage( ERenderViewStage::LIGHTING );
		//	pREL->SetParameterContainer( const_cast<ParameterContainer*>(pLight->GetParameterContainer()) );
		//	pREL->SetLightModel( pModel );

		//	if ( BuildRenderEntries( pModel, pREL->GetRenderEntryList() ) != EErrorCode::OKAY )
		//	{
		//		PSX_PushError( "Failed to generate render stage entries for point light." );
		//	}
		//
		//	pRenderEntries->AddRenderEntry( pREL );

		//	pModel->Release();
		//}
		//else
		//{
		//	PSX_PushError( "Other light types not supported yet." );
		//	return EErrorCode::_ERROR;
		//}

		return EErrorCode::OKAY;
	}

	void RenderPathLightPrePass::ExecuteRenderView( RenderView *pView )
	{
		GraphicsPipeline *pPipeline = Engine::GetInstance()->GetRenderer()->GetPipeline();
		ComponentCamera *pCamera = pView->GetCamera();
		LightManager *pLights = pView->GetLightManager();

		// Process lights first
		if ( pCamera )
		{
			for ( SIZE_T i = 0; i < pLights->GetNumLights(); ++i )
			{
				const ComponentLight *pLight = pLights->GetLight( i );

			}
		}

		// Process render stages
		pView->ExecuteRenderStages( pPipeline );
	}

	EErrorCode::Type RenderPathLightPrePass::CreateCameraSettings( ComponentCamera *pCamera, CameraRenderPathSettingsLPP **ppOut )
	{
		if ( pCamera == PSX_NULL )
		{
			PSX_PushError( "pView is null." );
			return EErrorCode::INVALID_PARAMETER;
		}

		if ( ppOut == PSX_NULL )
		{
			PSX_PushError( "ppOut is null." );
			return EErrorCode::INVALID_PARAMETER;
		}

		// Look if it already exists
		CameraRPSettings *pCamSettings = FindCameraSettings( pCamera );

		if ( pCamSettings )
		{
			PSX_PushLog1( "Already exists." );
			return EErrorCode::ALREADY_EXISTS;
		}

		// All good. Create RenderPath settings for this window view
		CameraRenderPathSettingsLPP *pNewWVRPSettings = new CameraRenderPathSettingsLPP( pCamera );

		if ( pNewWVRPSettings == PSX_NULL )
		{
			PSX_PushError( "Out of memory." );
			return EErrorCode::MEMORY;
		}

		// Set the settings
		EErrorCode::Type retCode = pNewWVRPSettings->Initialize();

		if ( retCode != EErrorCode::OKAY )
		{
			delete pNewWVRPSettings;
			return retCode;
		}

		*ppOut = pNewWVRPSettings;

		// Lastly, create pair then store.
		CameraRPSettings newWVRPSettings( pCamera, pNewWVRPSettings );
		m_viewRPSettings.PushBack( newWVRPSettings );

		return EErrorCode::OKAY;
	}

	EErrorCode::Type RenderPathLightPrePass::RemoveCameraSettings( const ComponentCamera *pCamera, const CameraRenderPathSettingsLPP *pSettings )
	{
		for ( SIZE_T i = 0; i < m_viewRPSettings.GetSize(); ++i )
		{
			CameraRPSettings *pPair = &m_viewRPSettings[i];

			if ( pPair->first == pCamera && pPair->second == pSettings )
			{
				// Found it
				delete pPair->second;
				m_viewRPSettings.Remove( i );
				return EErrorCode::OKAY;
			}
		}

		return EErrorCode::NOT_FOUND;
	}

}